#include "algv.h"

#include <time.h>
#include <vector>
#include <string.h>
#include <unistd.h>

#include "hw_app_config.h"
#include "hw_app_log.h"
#include "cv_utils.h"
#include "spdlog/stopwatch.h"

void to_json(nlohmann::json& j, const GC_BERTH& p) {
    j = nlohmann::json{{"name", p.name}, {"roi", p.roi}};
}

void from_json(const nlohmann::json& j, GC_BERTH& p) {
    j.at("name").get_to(p.name);
    j.at("roi").get_to(p.roi);
}

void to_json(nlohmann::json& j, const GC_CAMERA_CONFIG& p) {
    j = nlohmann::json{{"sn", p.sn}, {"ip", p.ip}, {"name", p.name}, {"roi", p.roi}, {"model", p.model}, {"mode", p.mode}, {"pos", p.pos}, 
        {"style", p.style}, {"state", p.state}, {"debug", p.debug}, {"berthes", p.berthes}, {"road", p.road}, {"alarm_roi", p.alarm_roi},
        {"area", p.area}
    //  {"rotaion", p.rotation}
    };
}

void from_json(const nlohmann::json& j, GC_CAMERA_CONFIG& p) {
    j.at("sn").get_to(p.sn);
    j.at("ip").get_to(p.ip);
    j.at("name").get_to(p.name);
    if (!j.at("roi").empty()) {
        j.at("roi").get_to(p.roi);
    }
    j.at("model").get_to(p.model);
    j.at("mode").get_to(p.mode);
    j.at("pos").get_to(p.pos);
    j.at("style").get_to(p.style);
    j.at("state").get_to(p.state);
    j.at("debug").get_to(p.debug);
    if (!j.at("berthes").empty()) {
        j.at("berthes").get_to(p.berthes);
    }
    // j.at("rotation").get_to(p.rotation);
    j.at("road").get_to(p.road);
    if (!j.at("alarm_roi").empty()) {
        j.at("alarm_roi").get_to(p.alarm_roi);
    }
    if (!j.at("area").empty()) {
        j.at("area").get_to(p.area);
    }
}

void to_json(nlohmann::json& j, const GC_APP_ATTACHMENT& p) {
    j = nlohmann::json{{"name", p.name}, {"url", p.url}, {"tag", p.tag}, {"uploadTs", p.upload_ts}, {"attachmentType", p.attachment_type}};
}

void from_json(const nlohmann::json& j, GC_APP_ATTACHMENT& p) {
    j.at("name").get_to(p.name);
    j.at("url").get_to(p.url);
    j.at("tag").get_to(p.tag);
    j.at("uploadTs").get_to(p.upload_ts);
    j.at("attachmentType").get_to(p.attachment_type);
}

void to_json(nlohmann::json& j, const GC_ALG_CONFIG& p) {
    j = nlohmann::json{{"default_yuv_img_w", p.default_yuv_img_w}, {"default_yuv_img_h", p.default_yuv_img_h}, {"subscribe_yuv_img_w", p.subscribe_yuv_img_w},
     {"subscribe_yuv_img_h", p.subscribe_yuv_img_h}, {"subscribe_yuv_fps", p.subscribe_yuv_fps}, {"yolo_conf_thresh", p.yolo_conf_thresh}, 
    //  {"model_path", p.model_path},
     {"classes", p.classes}, {"plate_classes", p.plate_classes}, {"car_classes", p.car_classes},
     {"track_max_idle_obj", p.track_max_idle_obj}, {"track_max_obj", p.track_max_obj}, {"track_max_idle_time", p.track_max_idle_time}, {"track_same_plate_iou_thresh", p.track_same_plate_iou_thresh},
     {"priority_province_chinese", p.priority_province_chinese}, {"priority_province_char", p.priority_province_char}, {"chinese_thres", p.chinese_thres}, {"chinese_thres_for_char", p.chinese_thres_for_char},
     {"lpr_need_detect", p.lpr_need_detect}, {"lpr_need_transformer", p.lpr_need_transformer},
     {"cacheDir", p.cache_dir}, {"cachePosition", p.cache_position}, {"imgCacheSize", p.img_cache_size}
     };
}

void from_json(const nlohmann::json& j, GC_ALG_CONFIG& p) {
    j.at("default_yuv_img_w").get_to(p.default_yuv_img_w);
    j.at("default_yuv_img_h").get_to(p.default_yuv_img_h);
    // j.at("subscribe_yuv_img_w").get_to(p.subscribe_yuv_img_w);
    // j.at("subscribe_yuv_img_h").get_to(p.subscribe_yuv_img_h);
    j.at("subscribe_yuv_fps").get_to(p.subscribe_yuv_fps);
    j.at("yolo_conf_thresh").get_to(p.yolo_conf_thresh);
    // j.at("model_path").get_to(p.model_path);
    // if (!j.at("classes").empty()) {
    //     j.at("classes").get_to(p.classes);
    // }
    // if (!j.at("plate_classes").empty()) {
    //     j.at("plate_classes").get_to(p.classes);
    // }
    // if (!j.at("car_classes").empty()) {
    //     j.at("car_classes").get_to(p.classes);
    // }
    j.at("track_max_idle_obj").get_to(p.track_max_idle_obj);
    j.at("track_max_obj").get_to(p.track_max_obj);
    j.at("track_max_idle_time").get_to(p.track_max_idle_time);
    j.at("track_same_plate_iou_thresh").get_to(p.track_same_plate_iou_thresh);
    j.at("priority_province_chinese").get_to(p.priority_province_chinese);
    j.at("priority_province_char").get_to(p.priority_province_char);
    j.at("chinese_thres").get_to(p.chinese_thres);
    j.at("chinese_thres_for_char").get_to(p.chinese_thres_for_char);
    j.at("lpr_need_detect").get_to(p.lpr_need_detect);
    j.at("lpr_need_transformer").get_to(p.lpr_need_transformer);
    j.at("cacheDir").get_to(p.cache_dir);
    j.at("cachePosition").get_to(p.cache_position);
    j.at("imgCacheSize").get_to(p.img_cache_size);
}

void to_json(nlohmann::json& j, const GC_BASE_CONFIG& p) {
    nlohmann::json alg_config = p.alg_config;
    j = nlohmann::json{{"uploadStateImage", p.upload_state_image}, {"middlewareHost", p.middleware_host}, {"middlewareUploadImagePeriod", p.middleware_upload_image_period}, 
     {"middlewareUploadImageApi", p.middleware_upload_image_api}, {"middlewareUploadInoutApi", p.middleware_upload_inout_api}, {"lprHost", p.lpr_host}, {"lprApi", p.lpr_api},
     {"attachmentList", p.attachment_list}, {"ftpHost", p.ftp_host}, {"algConfig", alg_config}
     };
}

void from_json(const nlohmann::json& j, GC_BASE_CONFIG& p) {
    j.at("uploadStateImage").get_to(p.upload_state_image);
    j.at("middlewareHost").get_to(p.middleware_host);
    j.at("middlewareUploadImagePeriod").get_to(p.middleware_upload_image_period);
    j.at("middlewareUploadImageApi").get_to(p.middleware_upload_image_api);
    j.at("middlewareUploadInoutApi").get_to(p.middleware_upload_inout_api);
    j.at("lprHost").get_to(p.lpr_host);
    j.at("lprApi").get_to(p.lpr_api);
    
    if (!j.at("attachmentList").empty()) {
        j.at("attachmentList").get_to(p.attachment_list);
    }

    j.at("ftpHost").get_to(p.ftp_host);
    
    if (!j.at("algConfig").empty()) {
        j.at("algConfig").get_to<GC_ALG_CONFIG>(p.alg_config);
    }
}

//  替换指定字符串
static std::string string_replace(std::string source, std::string find, std::string replace)
{
    std::string::size_type pos = 0;
    std::string::size_type a = find.size();
    std::string::size_type b = replace.size();
    while ((pos = source.find(find, pos)) != std::string::npos) {
        source.replace(pos, a, replace);
        pos += b;
    }
    return source;
}

namespace gc
{
    static inline string readFileIntoString(const string& path)
    {
        auto ss = ostringstream{};
        ifstream input_file(path);
        if (!input_file.is_open()) {
            LOG_ERROR("Could not open the file, path=%s", path.c_str());
            return "";
        }
        ss << input_file.rdbuf();
        return ss.str();
    }

    GCAlgV::GCAlgV()
    {
        m_client_camera = new httplib::Client("unix:/tmp/http_proxy_connect.socket");
        m_client_camera->set_default_headers({ { "Host", gc_app_config.algv_host.c_str() }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
        m_client_camera->set_keep_alive(false);
        m_client_camera->set_compress(true);
        m_client_camera->set_logger([](const httplib::Request& req, const httplib::Response& res) {
            LOG_DEBUG("http request: method=%s, %s:%d%s, body=%s", req.method.c_str(), req.remote_addr.c_str(), req.remote_port, req.path.c_str(), req.body.c_str());
            LOG_INFO("http response: status=%d, body=%s", res.status, res.body.c_str());
        });

        m_client_app_config = new httplib::Client("unix:/tmp/http_proxy_connect.socket");
        m_client_app_config->set_default_headers({ { "Host", gc_app_config.algv_host.c_str() }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
        m_client_app_config->set_keep_alive(false);
        m_client_app_config->set_compress(true);
        m_client_app_config->set_logger([](const httplib::Request& req, const httplib::Response& res) {
            LOG_DEBUG("http request: method=%s, %s:%d%s, body=%s", req.method.c_str(), req.remote_addr.c_str(), req.remote_port, req.path.c_str(), req.body.c_str());
            LOG_INFO("http response: status=%d, body=%s", res.status, res.body.c_str());
        });
    }

    GCAlgV::~GCAlgV(void)
    {
        delete m_client_camera;
        delete m_client_app_config;
    }

    void GCAlgV::SyncCameraConfig(const std::string camera_key, const char* img_path)
    {
        spdlog::stopwatch sw;

        std::time_t ts = std::time(nullptr);

        httplib::MultipartFormDataItems items = {
            { "sn", camera_key, "", "" },
            { "ip", gc_app_config.camera_config_map[camera_key].ip, "", "" },
            { "model", GC_CAMERA_MODEL::MODEL_UNKNOWN == gc_app_config.camera_config_map[camera_key].model ? "" : to_string(gc_app_config.camera_config_map[camera_key].model), "", "" },
            { "mode", to_string(gc_app_config.camera_config_map[camera_key].mode), "", "" },
            { "ts", to_string(ts), "", "" }, // TODO 暂时还没用到，后面用于校验与服务器的时间差等
            { "appStartTime", app_start_time, "", "" }, // 算法启动时间
            { "buildInfo", BUILD_INFO, "", "" }, // 算法应用编译信息
        };

        if (img_path != nullptr) {
            string fileContent = readFileIntoString(std::string(img_path));
            if (!fileContent.empty()) {
                items.push_back({ "hasImage", "Y", "", "" });
                items.push_back({ "file", fileContent, gc_app_config.camera_config_map[camera_key].sn + ".jpg", "application/octet-stream" });
            }
        }
        auto res = m_client_camera->Post(gc_app_config.algv_sync_camera_api.c_str(), items);

        int retry_time = 0;

        while ((res == nullptr || res->status != 200) && retry_time < 3) {

            LOG_WARN("POST syncCameraBySN api failed, algv_host=%s, status=%d", gc_app_config.algv_host.c_str(), res == nullptr ? -1 : res->status);

            usleep(100000);
            res = m_client_camera->Post(gc_app_config.algv_sync_camera_api.c_str(), items);
            retry_time ++;
        }
        items.clear();

        nlohmann::json result_json;
        if (res == nullptr) {
            LOG_ERROR("POST syncCameraBySN api response is null, algv_host=%s", gc_app_config.algv_host.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("POST syncCameraBySN api response status=%d, algv_host=%s", res->status, gc_app_config.algv_host.c_str());
        } else {
            std::unique_lock<std::mutex> lock(m_mutex, std::defer_lock);
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
                int code = result_json["code"].get<int>();
                if (code != 0) {
                    LOG_ERROR("POST syncCameraBySN api response code=%d msg=%s", code, result_json["msg"].get<std::string>().c_str());
                    return;
                }
                auto camera_config = result_json["data"].get<GC_CAMERA_CONFIG>();
                // 这几项值不能被覆盖，否则会影响状态图上传时间的计算
                camera_config.has_new_state_image = gc_app_config.camera_config_map[camera_key].has_new_state_image;
                camera_config.last_status_image_save_time = gc_app_config.camera_config_map[camera_key].last_status_image_save_time;
                camera_config.new_state_image_time_str = gc_app_config.camera_config_map[camera_key].new_state_image_time_str;

                if (gc_app_config.camera_config_map[camera_key].needRestart(camera_config)) {
                    LOG_WARN("abort application after sleep cause the camera config updated and need restart");
                    sleep(5);
                    abort();
                }
                lock.lock();
                gc_app_config.camera_config_map[camera_key] = camera_config;
            }
            catch(const std::exception& e)
            {
                lock.unlock();
                LOG_ERROR("POST syncCameraBySN api parse response error, algv_host=%s, response=%s\n%s", gc_app_config.algv_host.c_str(), res->body.c_str(), e.what());

                return;
            }
            lock.unlock();
        }

        /*
        http response: status=200, body={"code":0,"data":{"ID":1159,"CreatedAt":"2022-08-16T13:37:22+08:00","UpdatedAt":"2022-08-16T16:21:25+08:00","road":"","model":2,"ip":"0.0.0.1","name":"","spot":"","mode":1,"style":1,"distance":2,"pos":1,"ftp":"","state":5,"osd":"","roi":null,"note":"","area":"xx","quick":null,"frameSize":[1920,1080],"debug":null,"frame_path":"/data8/algv/lpr_upload_images/dcf3e36ee8115282aad46485cab6a4be_20220816162158.jpg","sn":"071E18ED871CA315C3A5AC6E41A7F290","syncTime":"2022-08-16T16:21:58.80380757+08:00"},"msg":"操作成功"}
        */

        logger->info("SyncCameraConfig_time: {:.3} s", sw);

        return;
    }

    void GCAlgV::GetAppConfig(const char* area)
    {
        spdlog::stopwatch sw;

        // TODO 查看httplib文档，用优雅的方式实现params传递
        std::string api("/gcappconfig/getconfig?area="); // TODO configable 从相机的通用配置中获取
        api.append(area);
        // api.append("&model=");
        // api.append(to_string(model));
        api.append("&beginTs=");
        api.append(to_string(BUILD_TS));
        // api.append("&attachmentType=");
        // api.append(to_string(GC_ATTACHMENT_TYPE::ATTACH_TYPE_BIN));

        auto res = m_client_app_config->Get(api.c_str());

        int retry_time = 0;
        while ((res == nullptr || res->status != 200) && retry_time < 3) {

            LOG_WARN("GET getconfig api failed, algv_host=%s, status=%d", gc_app_config.algv_host.c_str(), res == nullptr ? -1 : res->status);

            usleep(100000);
            res = m_client_app_config->Get(api.c_str());
            retry_time ++;
        }
        nlohmann::json result_json;
        if (res == nullptr) {
            LOG_ERROR("GET getconfig api response is null, algv_host=%s", gc_app_config.algv_host.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("GET getconfig api response status=%d, algv_host=%s", res->status, gc_app_config.algv_host.c_str());
        } else {
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
                int code = result_json["code"].get<int>();
                if (code != 0) {
                    LOG_ERROR("GET getconfig api response code=%d msg=%s", code, result_json["msg"].get<std::string>().c_str());
                    return;
                }
                auto base_config = result_json["data"].get<GC_BASE_CONFIG>();
                // TODO 某些算法参数变更后，需要重启应用程序，注：最好判断当前是否有正在进行中的行为再进行重启
                std::vector<GC_APP_ATTACHMENT> attachment_list = base_config.attachment_list;
                if (attachment_list.size() > 0) {
                    httplib::Client client("unix:/tmp/http_proxy_connect.socket");
                    client.set_default_headers({ { "Host", base_config.ftp_host }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
                    for (auto attach: attachment_list) {
                        // 设定这个时区没起作用，可能用法有问题
                        unsetenv("TZ");
                        setenv("TZ", "Asia/Shanghai", 1);
                        tzset();
                        std::string patch_path = "";
                        switch (attach.attachment_type)
                        {
                            case GC_ATTACHMENT_TYPE::ATTACH_TYPE_BIN:
                                patch_path = "./bin/" + attach.name;
                                break;
                            case GC_ATTACHMENT_TYPE::ATTACH_TYPE_MODEL:
                                patch_path = "./model/" + attach.name;
                                break;
                            case GC_ATTACHMENT_TYPE::ATTACH_TYPE_LIB:
                                patch_path = "./lib/" + attach.name;
                                break;
                            case GC_ATTACHMENT_TYPE::ATTACH_TYPE_CONFIG:
                                patch_path = "./config/" + attach.name;
                                break;
                            default:
                                patch_path = attach.name;
                                break;
                        }
                        struct stat stat_result;
                        // TODO 这个获取的是UTC时间，没有实现获取本地或东8区时间的方法，目前时间戳进行了手动偏移处理。
                        if (stat(patch_path.c_str(), &stat_result) == 0) {
                            auto last_modified_time = stat_result.st_mtim.tv_sec - 8 * 60 * 60;
                            LOG_INFO("file=%s last_modified_time=%d", patch_path, last_modified_time);
                            if (last_modified_time >= attach.upload_ts) {
                                LOG_INFO("file=%s  last_modified_time ts=%ld is newer than patch ts=%d, ignore the patch file", patch_path, last_modified_time, attach.upload_ts);
                                continue;
                            }
                        }
                        std::ofstream ofs{patch_path + "_new"};
                        auto download_res = client.Get(attach.url.c_str(),
                            [&](const char *data, size_t dataLen) {
                                ofs.write(data, dataLen);
                                return true;
                        });
                        retry_time = 0;
                        while ((download_res == nullptr || download_res->status != 200) && retry_time < 2) {

                            LOG_WARN("download patch file failed, path=%s, status=%d", gc_app_config.base_config.ftp_host + attach.url, download_res == nullptr ? -1 : download_res->status);

                            usleep(100000);
                            download_res = client.Get(attach.url.c_str(),
                                [&](const char *data, size_t dataLen) {
                                    ofs.write(data, dataLen);
                                    return true;
                            });
                            retry_time ++;
                        }
                        ofs.flush();
                        ofs.close();
                        if (download_res == nullptr || download_res->status != 200) {
                            LOG_ERROR("download patch file failed, path=%s, status=%d", gc_app_config.base_config.ftp_host + attach.url, download_res == nullptr ? -1 : download_res->status);
                        } else {
                            // 注意：这里的日志进行了业务操作，不能随意删除
                            LOG_INFO("download patch file successful, path=%s, status=%d", gc_app_config.base_config.ftp_host + attach.url, download_res->status);
                            LOG_INFO("cp %s result:%d", patch_path.c_str(), std::system(("cp -f "  + patch_path + " " + patch_path + "_old ").c_str()));
                            LOG_INFO("chmod %s_new result:%d", patch_path.c_str(), std::system(("chmod 755 " + patch_path + "_new").c_str()));
                            LOG_INFO("cp %s_new result:%d", patch_path.c_str(), std::system(("cp -f " + patch_path + "_new " + patch_path).c_str()));
                            LOG_WARN("abort application after sleep cause the patch file has been updated");
                            sleep(5);
                            abort();
                        }
                    }
                }
                // std::lock_guard<std::mutex> lock(m_base_config_mutex);
                // FIXME 没有处理首次调用不一致情况，如果云端相关配置与默认值不同, 会一直重启
                // if (gc_app_config.base_config.needRestart(base_config)) {
                //     LOG_WARN("abort application after sleep cause the base config updated and need restart");
                //     sleep(5);
                //     abort();
                // }
                gc_app_config.base_config = base_config;
                gc_app_config.base_config.alg_config.yolo_conf_thresh = base_config.alg_config.yolo_conf_thresh * SAMPLE_SVP_NNIE_QUANT_BASE;
                // 这里取一台相机的信息做判断即可
                std::map<std::string, GC_CAMERA_CONFIG>::iterator iter = gc_app_config.camera_config_map.begin();
                do {
                    if (
                        iter->second.model == GC_CAMERA_MODEL::MODEL_M2141
                        || 
                        iter->second.model == GC_CAMERA_MODEL::MODEL_C6620
                        || 
                        iter->second.model == GC_CAMERA_MODEL::MODEL_C6650
                        || 
                        iter->second.model == GC_CAMERA_MODEL::MODEL_C2150
                        || 
                        iter->second.model == GC_CAMERA_MODEL::MODEL_UNKNOWN
                    ) {
                        gc_app_config.base_config.alg_config.low_fps();
                    }
                    if (iter->second.style == GC_CAMERA_STYLE::STYLE_BAN) {
                        gc_app_config.base_config.alg_config.park_fps();
                    }
                    if (iter->second.model == GC_CAMERA_MODEL::MODEL_C6650) {
                        gc_app_config.base_config.alg_config.img_cache_size = 160;
                    }
                    // 处理走廊模式订阅宽高
                    gc_app_config.base_config.alg_config.subscribe_yuv_img_w = iter->second.mode == GC_CAMERA_MODE::MODE_ZL ? gc_app_config.base_config.alg_config.default_yuv_img_h : gc_app_config.base_config.alg_config.default_yuv_img_w;
                    gc_app_config.base_config.alg_config.subscribe_yuv_img_h = iter->second.mode == GC_CAMERA_MODE::MODE_ZL ? gc_app_config.base_config.alg_config.default_yuv_img_w : gc_app_config.base_config.alg_config.default_yuv_img_h;
                } while (0);

                // 替换配置项中的变量参数
                gc_app_config.base_config.middleware_upload_image_api = string_replace(gc_app_config.base_config.middleware_upload_image_api, "${sn}", gc_app_config.hardware_id);
                gc_app_config.base_config.middleware_upload_inout_api = string_replace(gc_app_config.base_config.middleware_upload_inout_api, "${sn}", gc_app_config.hardware_id);

                nlohmann::json c = gc_app_config.base_config;
                LOG_INFO("gc_app_config.base_config: %s", c.dump().c_str());
            }
            catch(const std::exception& e)
            {
                LOG_ERROR("POST getconfig api parse response error, algv_host=%s, response=%s\n%s", gc_app_config.algv_host.c_str(), res->body.c_str(), e.what());
                return;
            }
        }

        logger->info("GetConfig_time: {:.3} s", sw);

        return;
    }
}
